﻿namespace Blaze.Framework.LuaExtensions
{
    using System;
    using System.Collections.Generic;
    using Pooling;
    using UnityEngine;
    using XLua;

    /// <summary>
    /// 用于传递Unity的相关事件到Lua脚本。
    /// </summary>
    public partial class LuaBehaviour : MonoBehaviour, ISerializationCallbackReceiver
    {
        /// <summary>
        /// 是否自动注入字段引用到脚本。
        /// </summary>
        [HideInInspector]
        public bool AutoInject = true;

        /// <summary>
        /// 所有在编辑器中定义的字段。
        /// </summary>
        [HideInInspector]
        public List<LuaFieldPair> Fields = new List<LuaFieldPair>();

        /// <summary>
        /// lua脚本相对路径。
        /// </summary>
        public string LuaFilePath = string.Empty;

        /// <summary>
        /// 获取关联的<see cref="LuaTable"/>。
        /// </summary>
        public LuaTable LuaTable { get; private set; }

        /// <summary>
        /// 获取当前<see cref="LuaBehaviour"/>所使用的Lua模块。
        /// </summary>
        protected virtual ILuaModule LuaModule
        {
            get { return Bootstrap.Instance.LuaModule; }
        }

        #region ISerializationCallbackReceiver Members

        /// <summary>
        /// <para>
        /// Implement this method to receive a callback after Unity de-serializes your object.
        /// </para>
        /// </summary>
        public void OnAfterDeserialize()
        {
            mFields.Clear();
            for (var i = 0; i < Fields.Count; i++)
                mFields.Add(Fields[i].K, Fields[i]);
        }

        /// <summary>
        /// <para>
        /// Implement this method to receive a callback before Unity serializes your object.
        /// </para>
        /// </summary>
        public void OnBeforeSerialize()
        {
        }

        #endregion

        /// <summary>
        /// 根据设置好的<see cref="LuaFilePath"/>加载Lua脚本。
        /// </summary>
        /// <returns>是否加载成功</returns>
        public bool Initialize()
        {
            if (mIsInitialized)
                return true;
            mIsInitialized = true;

            if (LuaTable == null)
            {
                var module = LuaModule.Require(LuaFilePath);
                if (module == null)
                {
                    Debug.LogError("Lua table not found -> " + LuaFilePath);
                    return false;
                }

                var constructor = module.Get<LuaFunction>("new");
                if (constructor == null)
                {
                    Debug.LogError(LuaFilePath + ".new() function required");
                    return false;
                }

                LuaTable = constructor.Func<LuaTable>();

                if (LuaTable == null)
                {
                    Debug.LogError(LuaFilePath + " Invalid new() function");
                    return false;
                }
            }

            mFunctionCache = mFunctionCachePool.Get();
            mFunctionCache.LuaTable = LuaTable;

            LuaTable.Set("transform", transform);
            LuaTable.Set("gameObject", gameObject);
            LuaTable.Set("behaviour", this);

            if (AutoInject)
                injectFields();

            mFunctionCache.Action("Awake");

            if (gameObject.activeInHierarchy)
                mFunctionCache.Action("OnEnable");

            LuaModule.Reloading += onReloadingLua;
            return true;
        }

        /// <summary>
        /// 提供Animation上的事件触发机制。
        /// </summary>
        /// <param name="eventContent">事件名称,事件参数</param>
        public void LuaAnimationEvent(string eventContent)
        {
            if (!Initialize())
                return;
            if (string.IsNullOrEmpty(eventContent))
                throw new ArgumentException("eventContent");

            var splitedContent = eventContent.Split(',');
            mFunctionCache.Action("OnAnimationEvent", splitedContent);
        }

#if UNITY_EDITOR
        /// <summary>
        /// 在lua中实现实现OnInspectorGUI来自己画编辑器界面。
        /// </summary>
        public void OnInspectorGUI()
        {
            //credit to heroboy
            if (!Application.isPlaying || !mIsInitialized)
                return;

            try
            {
                mFunctionCache.Action("OnInspectorGUI");
            }
            catch (LuaException e)
            {
                UnityEditor.EditorGUILayout.HelpBox(e.Message, UnityEditor.MessageType.Error);
            }
        }
#endif

        private void injectFields()
        {
            foreach (var pair in Fields)
            {
                switch (pair.T)
                {
                    case LuaFieldType.GameObject:
                        LuaTable.Set(pair.K, pair.V);
                        break;
                    case LuaFieldType.String:
                        LuaTable.Set(pair.K, pair.V1);
                        break;
                    case LuaFieldType.Number:
                        LuaTable.Set(pair.K, pair.V2);
                        break;
                    case LuaFieldType.Boolean:
                        LuaTable.Set(pair.K, pair.V2 != 0);
                        break;
                    case LuaFieldType.LuaTable:
                        var behaviour = (LuaBehaviour) pair.V;
                        behaviour.Initialize();
                        LuaTable.Set(pair.K, behaviour.LuaTable);
                        break;
                    case LuaFieldType.Component:
                    case LuaFieldType.Asset:
                        LuaTable.Set(pair.K, pair.V);
                        break;
                }
            }
        }

        private void onReloadingLua()
        {
            LuaModule.Reloading -= onReloadingLua;
            Destroy(gameObject);
        }

        private readonly Dictionary<string, LuaFieldPair> mFields = new Dictionary<string, LuaFieldPair>();
        private bool mIsInitialized;
        private FunctionCache mFunctionCache;
        private static readonly PoolableObjectPool<FunctionCache> mFunctionCachePool = new PoolableObjectPool<FunctionCache>();

        #region Unity built-in methods

        protected void Start()
        {
            if (!Initialize())
                return;

            if (gameObject.activeInHierarchy)
                mFunctionCache.Action("Start");
        }

        protected void Update()
        {
            mFunctionCache.Action("Update");
        }

        protected void LateUpdate()
        {
            mFunctionCache.Action("LateUpdate");
        }

        protected void FixedUpdate()
        {
            mFunctionCache.Action("FixedUpdate");
        }

        protected void OnEnable()
        {
            mFunctionCache.Action("OnEnable");
        }

        protected void OnDisable()
        {
            mFunctionCache.Action("OnDisable");
        }

        protected void OnDestroy()
        {
            if (!mIsInitialized)
                return;

            mFunctionCache.Action("OnDestroy");
            mFunctionCachePool.Put(mFunctionCache);

            if (LuaTable != null)
                LuaTable.Dispose();

            mIsInitialized = false;
        }

        #endregion
    }
}